package com.base.springboot.starter.web.config;


import com.base.common.enums.ResponseCodeEnum;
import com.base.common.exception.CommonRuntimeException;
import com.base.common.model.dto.BaseDto;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;

/**
 * controller 全局异常捕获
 * 注意：如果要添加更多的新异常类型，需要确保新异常能被识别出来，见 ExceptionTransformer.ifThrowCheckedException
 *
 * @author admin
 * @date 2020/5/19 AM10:15
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 参数异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseDto validationBodyException(MethodArgumentNotValidException e) {
        StringBuilder buffer = new StringBuilder();
        BindingResult result = e.getBindingResult();
        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
            errors.forEach(p -> {
                FieldError fieldError = (FieldError) p;
                buffer.append(fieldError.getField()).append(fieldError.getDefaultMessage()).append(";");
            });
        }
        return BaseDto.buildFailure("GLOBAL-VALID-PARAM-412", buffer.toString().substring(0, buffer.toString().length() - 1));
    }

    /**
     * 权限异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler({SecurityException.class})
    public BaseDto error(SecurityException e) {
        return BaseDto.buildFailure("GLOBAL-SECURITY-401", e.getMessage());
    }

    /**
     * 业务异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(value = CommonRuntimeException.class)
    public BaseDto businessErrorHandler(CommonRuntimeException e) {
        return BaseDto.buildFailure(e.getCode(), e.getMessage());
    }

    /**
     * 全局异常处理
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    private BaseDto exceptionHandler(HttpServletRequest request, Exception e) {
        return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), Fields.GLOBAL_FAIL_MSG);
    }

    /**
     * 参数校验异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(value = IllegalArgumentException.class)
    public BaseDto paramErrorHandler(IllegalArgumentException e) {
        return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), e.getMessage());
    }

    /**
     * PersistenceException 异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(value = PersistenceException.class)
    public BaseDto duplicateErrorHandler(PersistenceException e) {
        if (e.getMessage().contains(Fields.BATCH_UPDATE_EXCEPTION)) {
            return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), "数据重复");
        } else {
            return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), Fields.GLOBAL_FAIL_MSG);
        }
    }

    /**
     * MissingServletRequestParameterException 异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public BaseDto missingServletParamErrorHandler(MissingServletRequestParameterException e) {
        String errorLog = "数据类型:" + e.getParameterType() + " 参数 '" + e.getParameterName() + "' 不存在";
        return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), errorLog);
    }

    /**
     * SQLIntegrityConstraintViolationException 异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(value = SQLIntegrityConstraintViolationException.class)
    public BaseDto duplicateErrorHandler(SQLIntegrityConstraintViolationException e) {
        if (e.getMessage().contains(Fields.UNRECOGNIZED_EXCEPTION)) {
            return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), "数据重复");
        } else {
            return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), Fields.GLOBAL_FAIL_MSG);
        }
    }

    /**
     * UnrecognizedPropertyException 异常
     *
     * @param e 异常
     * @return 响应
     */
    @ResponseBody
    @ExceptionHandler(value = UnrecognizedPropertyException.class)
    public BaseDto duplicateErrorHandler(UnrecognizedPropertyException e) {
        if (e.getMessage().contains(Fields.DUPLICATE_EXCEPTION)) {
            return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), "不可识别的属性");
        } else {
            return BaseDto.buildFailure(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), Fields.GLOBAL_FAIL_MSG);
        }
    }

    public static final class Fields {
        private Fields() {
        }

        /**
         * UnrecognizedPropertyException
         */
        public static final String UNRECOGNIZED_EXCEPTION = "UnrecognizedPropertyException: Unrecognized field";
        /**
         * SQLIntegrityConstraintViolationException
         */
        public static final String DUPLICATE_EXCEPTION = "java.sql.SQLIntegrityConstraintViolationException: Duplicate entry";
        /**
         * BatchUpdateException
         */
        public static final String BATCH_UPDATE_EXCEPTION = "java.sql.BatchUpdateException: Duplicate entry";
        /**
         * 全局异常日志
         */
        public static final String GLOBAL_LOG_MSG = "全局异常处理，系统异常";
        /**
         * 全局错误提示语
         */
        public static final String GLOBAL_FAIL_MSG = "服务器异常，请联系管理员";
    }
}